package com.azh.server.tables;

import java.util.Arrays;
import java.util.List;

import com.azh.server.im.BTree;
import com.azh.server.parser.statement.SingleExpression;
import com.azh.server.tx.TxManagerImpl;
import com.azh.server.util.ExceptionUtils;
import com.azh.server.util.Parser;
import com.azh.server.util.ParserStr;
import com.google.common.primitives.Bytes;

/**
 * @author zhaozhenhang <zhaozhenhang@kuaishou.com>
 * Created on 2022-05-01
 * 该类表示字段信息，格式为
 * [FieldName][TypeName][IndexUid]
 * 如果field没有索引，则IndexUid = 0
 */
public class Field {

    public long uid;

    private Table table;

    public String fieldName;

    public String fieldType;

    private long index;

    private BTree bTree;

    public Field(long uid, Table table) {
        this.uid = uid;
        this.table = table;
    }

    public Field(Table table, String fieldName, String fieldType, long index) {
        this.table = table;
        this.fieldName = fieldName;
        this.fieldType = fieldType;
        this.index = index;
    }

    public static Field loadField(Table table, long uid) {
        byte[] raw = null;
        try {
            raw = ((TableManagerImpl) table.tableM).vm.read(TxManagerImpl.SUPER_TX_ID, uid);
        } catch (Exception e) {
            ExceptionUtils.getMessage(e);
        }
        return new Field(uid, table).parseSelf(raw);
    }

    public static Field createField(Table tb, long xid, String fieldName, String fieldType, boolean indexed) throws Exception {
        typeCheck(fieldType);
        Field f = new Field(tb, fieldName, fieldType, 0);
        if(indexed) {
            long index = BTree.create(((TableManagerImpl) tb.tableM).dm);
            BTree bt = BTree.load(index, ((TableManagerImpl) tb.tableM).dm);
            f.index = index;
            f.bTree = bt;
        }
        f.persistSelf(xid);
        return f;
    }

    public Object stringToValue(String str) {
        switch(fieldType) {
            case "int32":
                return Integer.parseInt(str);
            case "int64":
                return Long.parseLong(str);
            case "string":
                return str;
        }
        return null;
    }

    public byte[] valueToRaw(Object v) {
        byte[] raw = null;
        switch(fieldType) {
            case "int32":
                raw = Parser.intToByte((int)v);
                break;
            case "int64":
                raw = Parser.longToByte((long)v);
                break;
            case "string":
                raw = Parser.stringToByte((String)v);
                break;
        }
        return raw;
    }

    public boolean isIndexed() {
        return index != 0;
    }

    public void insert(Object obj, long uid) throws Exception {
        long key = valueToUid(obj);
        bTree.insert(key, uid);
    }

    public FieldCalRes calExp(SingleExpression exp) throws Exception {
        Object v = null;
        FieldCalRes res = new FieldCalRes();
        switch(exp.compareOP) {
            case "<":
                res.left = 0;
                v = stringToValue(exp.value);
                res.right = valueToUid(v);
                if(res.right > 0) {
                    res.right --;
                }
                break;
            case "=":
                v = stringToValue(exp.value);
                res.left = valueToUid(v);
                res.right = res.left;
                break;
            case ">":
                res.right = Long.MAX_VALUE;
                v = stringToValue(exp.value);
                res.left = valueToUid(v) + 1;
                break;
        }
        return res;
    }

    class ParseValueRes {
        Object v;
        int shift;
    }

    public String printValue(Object v) {
        String str = null;
        switch(fieldType) {
            case "int32":
                str = String.valueOf((int)v);
                break;
            case "int64":
                str = String.valueOf((long)v);
                break;
            case "string":
                str = (String)v;
                break;
        }
        return str;
    }

    public ParseValueRes parserValue(byte[] raw) {
        ParseValueRes res = new ParseValueRes();
        switch(fieldType) {
            case "int32":
                res.v = Parser.parseInt(Arrays.copyOf(raw, 4));
                res.shift = 4;
                break;
            case "int64":
                res.v = Parser.parseLong(Arrays.copyOf(raw, 8));
                res.shift = 8;
                break;
            case "string":
                ParserStr r = Parser.parserStr(raw);
                res.v = r.msg;
                res.shift = r.next;
                break;
        }
        return res;
    }


    public List<Long> search(long left, long right) throws Exception {
        return bTree.searchRange(left, right);
    }

    public long valueToUid(Object key) {
        long uid = 0;
        switch(fieldType) {
            case "string":
                uid = Parser.strToUUID((String)key);
                break;
            case "int32":
                int uint = (int)key;
                return (long)uint;
            case "int64":
                uid = (long)key;
                break;
        }
        return uid;
    }

    private void persistSelf(long xid) throws Exception {
        byte[] nameRaw = Parser.stringToByte(fieldName);
        byte[] typeRaw = Parser.stringToByte(fieldType);
        byte[] indexRaw = Parser.longToByte(index);
        this.uid = ((TableManagerImpl) table.tableM).vm.insert(xid, Bytes.concat(nameRaw, typeRaw, indexRaw));
    }

    private static void typeCheck(String fieldType) throws Exception {
        if(!"int32".equals(fieldType) && !"int64".equals(fieldType) && !"string".equals(fieldType)) {
            throw new RuntimeException("Invalid field type!");
        }
    }

    private Field parseSelf(byte[] raw) {
        int position = 0;
        ParserStr res = Parser.parserStr(raw);
        fieldName = res.msg;
        position += res.next;
        res = Parser.parserStr(Arrays.copyOfRange(raw, position, raw.length));
        fieldType = res.msg;
        position += res.next;
        this.index = Parser.parseLong(Arrays.copyOfRange(raw, position, position+8));
        if(index != 0) {
            try {
                bTree = BTree.load(index, ((TableManagerImpl)table.tableM).dm);
            } catch(Exception e) {
                ExceptionUtils.getMessage(e);
            }
        }
        return this;
    }
}
